package dispatch

import (
	"context"
	"fmt"
	"math"
	"order_srv/handler/dispatch/internal"
	"time"
)

// Params 可配置参数（从 Redis/DB 读取，支持城市/分组维度）
type Params struct {
	CityId              uint32
	Strategy            string
	MaxDistanceKM       float64
	ScoreWeightDistance float64
	ScoreWeightRating   float64
	ScoreWeightAccept   float64
	DayStartHour        int
	NightStartHour      int
	TimeWindowMinutes   int
	GrayGroups          []string
	Version             string
}

// Decision 决策结果
type Decision struct {
	DriverId uint32
	Score    float64
	Reason   string
	Version  string
}

// Engine 派单引擎
type Engine struct{}

func NewEngine() *Engine { return &Engine{} }

// Dispatch 核心派单：基于时间窗/GEO/评分
func (e *Engine) Dispatch(ctx context.Context, p Params, startLng, startLat float64, serviceTime time.Time, candidates []internal.DriverBrief) (*Decision, error) {
	// 基于时间窗过滤：司机在服务时间前后无冲突
	window := time.Duration(p.TimeWindowMinutes) * time.Minute
	filtered := make([]internal.DriverBrief, 0, len(candidates))
	for _, d := range candidates {
		if internal.IsDriverAvailable(ctx, d.Id, serviceTime.Add(-window), serviceTime.Add(window)) {
			filtered = append(filtered, d)
		}
	}
	if len(filtered) == 0 {
		return nil, fmt.Errorf("没有可用司机")
	}

	// 根据日夜权重差异，调整距离超时策略
	hour := serviceTime.Hour()
	isNight := hour >= p.NightStartHour || hour < p.DayStartHour
	// 计算得分
	var best internal.DriverBrief
	bestScore := -math.MaxFloat64
	for _, d := range filtered {
		dist := internal.HaversineKM(startLng, startLat, d.Lng, d.Lat)
		if dist > p.MaxDistanceKM {
			continue
		}
		// 距离越近分越高
		distanceScore := 100 - dist*10
		if isNight {
			distanceScore -= 5 // 夜间增加距离惩罚
		}
		score := distanceScore*p.ScoreWeightDistance + d.Rating*p.ScoreWeightRating + d.AcceptRate*p.ScoreWeightAccept
		if score > bestScore {
			bestScore = score
			best = d
		}
	}
	if best.Id == 0 {
		return nil, fmt.Errorf("无合格司机")
	}
	reason := fmt.Sprintf("distance=%.2fkm,rating=%.2f,accept=%.2f,score=%.2f", internal.HaversineKM(startLng, startLat, best.Lng, best.Lat), best.Rating, best.AcceptRate, bestScore)
	return &Decision{DriverId: best.Id, Score: bestScore, Reason: reason, Version: p.Version}, nil
}
